#include <process_struct.h>
#include <system_monitor.h>
#include <configuration.h>

#include <gtkmm.h>

#include <time.h>
#include <stdio.h>

#include <fstream>
#include <iostream>
using std::cerr;
using std::endl;
#include <algorithm>
using std::max;
using std::min;
using std::for_each;

#include <glibtop/close.h>
#include <glibtop/cpu.h>
#include <glibtop/proclist.h>
#include <glibtop/procstate.h>
#include <glibtop/proctime.h>
#include <glibtop/procmem.h>
#include <glibtop/sysinfo.h>
#include <glibtop/mem.h>

system_monitor::system_monitor (const configuration &n_config):
original_config_(n_config),
last_check_time_(0),
timeout_connection_(Glib::signal_timeout().connect
           (sigc::mem_fun (*this, &system_monitor::check_system_loads), 
            original_config_.soft_interval * 1000))
{
    glibtop_init ();

    /* get number of cpu */
    const glibtop_sysinfo *sysinfo = glibtop_get_sysinfo ();
    ncpu_ = sysinfo->ncpu;
}

system_monitor::~system_monitor (){
    glibtop_close ();
}

float system_monitor::get_sys_cpu_usage (){
    /* initial glibtop_cpu data */
    glibtop_cpu cpu;
    glibtop_get_cpu (&cpu);

    if (!cpu_total_time_last_){
        cpu_total_time_last_ = cpu.total;
        cpu_used_time_last_  = cpu.user + cpu.nice + cpu.sys;
        return 0.0;
    }

    /* get system load */
    float load;
    float total, used;

    total = cpu.total - cpu_total_time_last_;
    used  = cpu.user + cpu.nice + cpu.sys - cpu_used_time_last_;
    cpu_total_time_last_ = cpu.total;
    cpu_used_time_last_  = cpu.user + cpu.nice + cpu.sys;

    load  = 100 * used / max(total, 1.0f);
    load  = min (load, 100.0f);

    return load;
}

long system_monitor::get_sys_mem () {
    glibtop_mem mem;
    glibtop_get_mem (&mem);

    return mem.user /1024 /1024;
}

void system_monitor::get_sys_io (long *read, long *write) {
    std::ifstream io("/proc/vmstat");
    if (!io) return;

    char buffer[512];
    for (int i = 0; i < 23; i++) { /* skip to read_bytes and write_bytes */
        io.getline (buffer, 512);
    }

    io.getline (buffer, 512);
    *read = atol (buffer + 7);
    io.getline (buffer, 512);
    *write = atol (buffer + 8);
}

void
system_monitor::get_process_io 
(const char *proc, long long*read, long long*write)
{
    std::ifstream io(proc);
    if (!io) return;

    char buffer[512];
    for (int i = 0; i < 4; i++) { /* skip to read_bytes and write_bytes */
        io.getline (buffer, 512);
    }

    io.getline (buffer, 512);
    *read = atol (buffer + 12);
    io.getline (buffer, 512);
    *write = atol (buffer + 13);
}

bool system_monitor::check_system_loads () {
    /* save a configuration copy */
    now_time_ = time (NULL);
    config_.reset (new configuration (original_config_));
    time_t interval = now_time_ - last_check_time_;

    /* get system information */
    float cpu = get_sys_cpu_usage ();
    long mem = get_sys_mem ();
    long read_bytes, write_bytes;
    double read_speed = 0.0, write_speed = 0.0;

    get_sys_io (&read_bytes, &write_bytes);
    if (interval != 0) {
        read_speed = (read_bytes - read_bytes_last_)
                     /1024.0 /interval;
        write_speed = (write_bytes - write_bytes_last_)
                     /1024.0 /interval;
    }
    read_bytes_last_ = read_bytes;
    write_bytes_last_ = write_bytes;

    process_t sysinfo;
    sysinfo.pid = -2; /* reserved pid to indicate this is no process */
    sysinfo.memory = mem;
    sysinfo.cpu = cpu;
    sysinfo.read_bytes = read_speed;
    sysinfo.write_bytes = write_speed;

    /* send a signal */
    signal_sys_update.emit (sysinfo);

    /* check processes */
    /* in default, system cpu usage will be checked every 5 secs,
     * if it's too high, all processes will be examed to find the bad ones.
     * Otherwise, all processes will be went through every minute. */

    if (now_time_ - last_check_time_ < config_->hard_interval &&
        cpu < config_->cpu_valve &&
        read_speed < config_->io_valve &&
        write_speed < config_->io_valve )
    {
        go_through_bad_process (); 
        return true;
    }

    go_through_process ();
    last_check_time_ = now_time_;

    return true;
}

void system_monitor::go_through_process () {
    pid_t *pid_list;
    glibtop_proclist proclist;
    gint which, arg;

    which = GLIBTOP_KERN_PROC_ALL;
    arg   = 0;

    pid_list = glibtop_get_proclist (&proclist, which, arg); 

    for (size_t i = 0; i < proclist.number; i++)
        check_single_process (pid_list[i]);

    g_free (pid_list);
}

void system_monitor::go_through_bad_process () {
    /* flush unchecked process list */
    if (!unchecked_list_.empty ()) {
        process_store_t::iterator it;
        process_store_t::iterator end = unchecked_list_.end ();
        for (it = unchecked_list_.begin (); it != end; ++it)
            check_single_process (*it);
        unchecked_list_.clear ();
    } 

    if (warned_processes_.empty ()) return;

    /* check all bad processes, if one process dosen't exsit any more,
     * then remove it */

    /* IMPORTANT NOTE
     * we should never erase a iterator when iterating a container */

    /* EVIL CODE :
    process_store_t::iterator it;
    process_store_t::iterator end = warned_processes_.end ();

    for (it = warned_processes_.begin (); it != end; ++it)
        if (!check_single_process (*it)){
            signal_remove_process (*it);
            warned_processes_.erase (it);
        }
    */

    /* GOOD CODE */
    process_store_t tmp_set (warned_processes_);
    process_store_t::iterator it;
    process_store_t::iterator end = tmp_set.end ();

    for (it = tmp_set.begin (); it != end; ++it)
        if (!check_single_process (*it)){
            signal_remove_process (*it);
            warned_processes_.erase (*it);
        }
}

/* get information about the process and determine whether it's bad.
 * return false if the checked process does not exist. */
bool system_monitor::check_single_process (pid_t pid) {
    glibtop_proc_time  proctime;
    glibtop_proc_mem   procmem;

    glibtop_get_proc_time (&proctime, pid);
    if (!proctime.start_time) /* process doesn't exist */
        return false;

    /* insert process into list and get reference of it */
    process_data_t &process = process_list_[pid];

    /* get pcpu */
    float pcpu = 0.0;

    /* if the process is checked for the first time, add it to a list,
     * and check it later in a short time */
    if (!process.cpu_total_time)
    {
         unchecked_list_.insert (pid);
    } else {
        guint64 interval = cpu_total_time_last_ - process.cpu_total_time;
        if (interval > 0)
        {
            pcpu = ncpu_*(proctime.rtime - process.cpu_time)*100 / interval;
            pcpu = min (pcpu, 100.0f);
        } 
        else /* if interval < 0 then recheck it */
            unchecked_list_.insert (pid);
    }

    process.cpu_time        = proctime.rtime;
    process.cpu_total_time  = cpu_total_time_last_;

    /* get io */
    long long read_bytes, write_bytes;
    char buffer[512];
    snprintf (buffer, 512, "/proc/%d/io", pid);
    get_process_io (buffer, &read_bytes, &write_bytes);

    time_t interval = now_time_ - process.io_time_last;

    if (interval == 0) return true; /* sometimes interval is 0 */
    double read_speed = (read_bytes - process.read_bytes)
                        /1024.0 /1024 /interval;
    double write_speed = (write_bytes - process.write_bytes)
                        /1024.0 /1024 /interval;

    process.read_bytes      = read_bytes;
    process.write_bytes     = write_bytes;
    process.io_time_last    = now_time_;

    /* get memory */
    glibtop_get_proc_mem (&procmem, pid);
    long memres = procmem.resident / 1024 / 1024;

    if (pcpu > config_->cpu_valve ||
        memres > config_->memory_valve ||
        read_speed > config_->io_valve ||
        write_speed > config_->io_valve)
    {
        /* get process cmd */
        glibtop_proc_state procstate;
        glibtop_get_proc_state (&procstate, pid);

        process_t bad_process;
        bad_process.pid    = pid;
        bad_process.name   = procstate.cmd;
        bad_process.cpu    = pcpu;
        bad_process.memory = memres;
        bad_process.read_bytes = read_speed;
        bad_process.write_bytes = write_speed;

        send_signal_add (bad_process);
    } else {
        send_signal_rm (pid);
    }

    return true;
}

void system_monitor::send_signal_add (const process_t &process){
    process_store_t::const_iterator end = warned_processes_.end ();
    if (warned_processes_.find (process.pid) == end){
        warned_processes_.insert (process.pid); /* set the bit */
        signal_add_process.emit (process);
    } else {
    	signal_update_process.emit (process);
    }
}

void system_monitor::send_signal_rm (pid_t pid){
    /* if a warned process has become normal,
     * then send a signal */

    if (warned_processes_.erase (pid))
        signal_remove_process.emit (pid);
}
